package com.example.demo.ErrorController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 在SpringBoot中，Controller中拋出的異常默認交給/error來處理，
 * 應用程序可以將/error映射到一個特定的Controller中處理來代替SpringBoot的默認實現，
 * 應用可以繼承AbstractErrorController來統一處理系統的各種異常
 */
@Controller
public class ErrorController extends AbstractErrorController {

    Log log = LogFactory.getLog(ErrorController.class);

    @Autowired
    ObjectMapper objectMapper;

    public ErrorController() {
        super(new DefaultErrorAttributes());
    }

    @Override
    public String getErrorPath() {
        return null;
    }

    //重寫默認的錯誤返回信息頁面error
//    @RequestMapping("/error")
//    public ModelAndView getErrorPath(HttpServletRequest request, HttpServletResponse response) {
//        ModelAndView mv = new ModelAndView();
//        //處理異常
//        return mv;
//    }

    /**
     * 考慮到異常信息直接顯示給系統客服並不合適，尤其是RuntimeException。
     * 同時要區分頁面渲染和JSON請求的兩種情況，前者是返回一個頁面，後者是應該是返回一個JSON結果。
     * 因此，為應用提供統一的錯誤處理代碼如下：
     */
    @RequestMapping("/error")
    public ModelAndView getErrorPath(HttpServletRequest request,HttpServletResponse response){
        //獲取錯誤屬性集合
        Map<String,Object> model = Collections.unmodifiableMap(getErrorAttributes(request,false));
        //獲取異常，可能為空
        Throwable cause = getCause(request);
        //狀態
        int status = (Integer)model.get("status");
        //錯誤信息
        String message = (String)model.get("message");
        //友好提示
        String errorMessage = getErrorMessage(cause);
        //後台打印日誌信息方便查錯
        log.info(status+","+message,cause);

        response.setStatus(status);
        //判斷是否為JSON請求
        if(!isJsonRequest(request)){
            ModelAndView view = new ModelAndView("/error.html");
            view.addAllObjects(model);
            view.addObject("errorMessage",errorMessage);
            view.addObject("status",status);
            view.addObject("cause",cause);
            return view;
        }else{
            Map error = new HashMap();
            error.put("success",false);
            error.put("errorMessage",errorMessage);
            error.put("message",message);
            writeJson(response,error);
            return  null;
        }
    }

    /**
     *獲取應用系統的異常
     */
    protected Throwable getCause(HttpServletRequest request){
        Throwable error = (Throwable) request.getAttribute("Javax.servlet.error.exception");
        if(error!=null){
            //MVC 有可能會封裝異常成ServletException,需要調用getCause獲取真正的異常
            while(error instanceof ServletException && error.getCause() != null){
                error = ((ServletException)error).getCause();
            }
        }
        return error;
    }
    /**
     * 自定義異常來進一步輸出詳細信息
     */
    protected String getErrorMessage(Throwable ex){
        if(ex instanceof MyApplicationException){
            //如果MyApplicationException的信息可以展示給用戶
            return  ((MyApplicationException)ex).getMessage();
        }
        return "服務器錯誤，請聯繫管理員";
    }
    /**
     * 判斷客服端發起的是頁面渲染請求還是JSON請求
     */
    protected boolean isJsonRequest(HttpServletRequest request){
        //第一種方式
        /*String requestUri = (String)request.getAttribute("javax.servlet.error.request_uri");
        if(requestUri != null && requestUri.endsWith(".json")){//如果統一JSON請求後綴名用的.json表示
            return true;
        }else{
            return false;
        }*/
        //第二種方式
        boolean isJsonReq = request.getHeader("Accept").contains("application/json");
        return isJsonReq?true:false;
    }
    protected void writeJson(HttpServletResponse response,Object obj){
        try{
            String jsonStr = objectMapper.writeValueAsString(obj);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write(jsonStr);
            out.flush();
        }catch (Exception e){
            e.printStackTrace();
        }


    }
}
